using System;
using System.Drawing;
using System.IO;

using ExifLibrary;

namespace GPX
{
    public enum PhotoSize
    {
        Small,
        Medium,
        Large
    }

    public class PhotoInfo
    {
        /// <summary>
        /// Information on a photo to be mapped
        /// </summary>
        /// <param name="deltaT">Time difference to be added to photo time to get GPS time</param>
        public PhotoInfo(string fileName, TimeSpan deltaT)
        {
            FileInfo fi = new FileInfo(fileName);
            if (!fi.Exists)
            {
                throw new ApplicationException("The photo does not exist!");
            }

            _FullFileName = fileName;
            _FileName = Path.GetFileNameWithoutExtension(fileName);

            bool timeSet = false;

            try
            {
                _ExifFile = ExifFile.Read(fileName);
                if (_ExifFile.Properties.Keys.Contains(ExifLibrary.ExifTag.DateTimeOriginal))
                {
                    _PhotoTime = (DateTime)_ExifFile.Properties[ExifLibrary.ExifTag.DateTimeOriginal].Value;
                    timeSet = true;
                }
                else if (_ExifFile.Properties.Keys.Contains(ExifLibrary.ExifTag.DateTimeDigitized))
                {
                    _PhotoTime = (DateTime)_ExifFile.Properties[ExifLibrary.ExifTag.DateTimeDigitized].Value;
                    timeSet = true;
                }
                else if (_ExifFile.Properties.Keys.Contains(ExifLibrary.ExifTag.DateTime))
                {
                    _PhotoTime = (DateTime)_ExifFile.Properties[ExifLibrary.ExifTag.DateTime].Value;
                    timeSet = true;
                }

                _PreviouslyMapped = false;
                if (_ExifFile.Properties.Keys.Contains(ExifLibrary.ExifTag.GPSLatitude))
                {
                    GPSLatitudeLongitude latitude = _ExifFile.Properties[ExifTag.GPSLatitude] as GPSLatitudeLongitude;
                    GPSLatitudeRef latRef = GPSLatitudeRef.North;
                    if (_ExifFile.Properties.Keys.Contains(ExifTag.GPSLatitudeRef))
                    {
                        latRef = (GPSLatitudeRef)_ExifFile.Properties[ExifTag.GPSLatitudeRef].Value;
                    }
                    int degrees = (int)latitude.Degrees;
                    if (latRef == GPSLatitudeRef.South)
                        degrees = -(int)latitude.Degrees;
                    _Latitude = new Degree(degrees, (int)latitude.Minutes, (float)latitude.Seconds, Direction.Latitude);
                    _PreviouslyMapped = true;
                }
                if (_ExifFile.Properties.Keys.Contains(ExifLibrary.ExifTag.GPSLongitude))
                {
                    GPSLatitudeLongitude longitude = _ExifFile.Properties[ExifTag.GPSLongitude] as GPSLatitudeLongitude;
                    GPSLongitudeRef longRef = GPSLongitudeRef.East;
                    if (_ExifFile.Properties.Keys.Contains(ExifTag.GPSLongitudeRef))
                    {
                        longRef = (GPSLongitudeRef)_ExifFile.Properties[ExifTag.GPSLongitudeRef].Value;
                    }
                    int degrees = (int)longitude.Degrees;
                    if (longRef == GPSLongitudeRef.West)
                        degrees = -(int)longitude.Degrees;
                    _Longitude = new Degree(degrees, (int)longitude.Minutes, (float)longitude.Seconds, Direction.Longitude);
                    _PreviouslyMapped = _PreviouslyMapped & true;
                }
                else
                {
                    _PreviouslyMapped = false;
                }

                if (_ExifFile.Thumbnail != null)
                {
                    _Bitmap = _ExifFile.Thumbnail.ToImage() as Bitmap;
                    if (_Bitmap != null)
                        _Icon = Icon.FromHandle(_Bitmap.GetHicon());
                }
            }
            catch
            {
                //swallow
            }

            if (!timeSet)
            {
                _PhotoTime = fi.CreationTimeUtc;
            }
            _GPSTime = _PhotoTime + deltaT;
        }

        private string _FileName;
        public string FileName
        {
            get { return _FileName; }
        }

        private string _FullFileName;
        public string FullFileName
        {
            get { return _FullFileName; }
        }

        private ExifLibrary.ExifFile _ExifFile;

        private DateTime _PhotoTime;
        public DateTime PhotoTime
        {
            get { return _PhotoTime; }
        }

        private DateTime _GPSTime;
        public DateTime GPSTime
        {
            get { return _GPSTime; }
            set { _GPSTime = value; }
        }

        private bool _ShowOnMap;
        public bool ShowOnMap
        {
            get { return _ShowOnMap; }
            set { _ShowOnMap = value; }
        }

        private bool _HighLight;
        public bool HighLight
        {
            get { return _HighLight; }
            set { _HighLight = value; }
        }

        /// <summary>
        /// Adjust GPS time for photo
        /// </summary>
        /// <param name="deltaT">Time difference to be added to photo time to get GPS time</param>
        public void SetTimeDifference(TimeSpan deltaT)
        {
            _GPSTime = _PhotoTime + deltaT;
        }

        private bool _PreviouslyMapped;
        public bool PreviouslyMapped
        {
            get { return _PreviouslyMapped; }
        }

        private Degree _Longitude;
        public Degree Longitude
        {
            get { return _Longitude; }
            set
            {
                _Longitude = value;
                if (_ExifFile != null)
                {
                    _ExifFile.Properties[ExifTag.GPSLongitude] = new GPSLatitudeLongitude(ExifTag.GPSLongitude,
                        (float)Math.Abs(_Longitude.Degrees), (float)_Longitude.Minutes, (float)_Longitude.Seconds);
                    _ExifFile.Properties[ExifTag.GPSLongitudeRef] =
                        new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSLongitudeRef,
                        Math.Sign(_Longitude.Degrees) > 0 ? GPSLongitudeRef.East : GPSLongitudeRef.West);
                }
            }
        }

        private Degree _Latitude;
        public Degree Latitude
        {
            get { return _Latitude; }
            set
            {
                _Latitude = value;
                if (_ExifFile != null)
                {
                    _ExifFile.Properties[ExifTag.GPSLatitude] = new GPSLatitudeLongitude(ExifTag.GPSLatitude,
                        (float)Math.Abs(_Latitude.Degrees), (float)_Latitude.Minutes, (float)_Latitude.Seconds);
                    _ExifFile.Properties[ExifTag.GPSLatitudeRef] =
                        new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSLatitudeRef,
                        Math.Sign(_Latitude.Degrees) > 0 ? GPSLatitudeRef.North : GPSLatitudeRef.South);
                }
            }
        }

        private Bitmap _Bitmap;
        public Bitmap Bitmap
        {
            get { return _Bitmap; }
        }

        private Icon _Icon;
        public Icon Icon
        {
            get { return _Icon; }
        }

        public Size GetSize(PhotoSize ps)
        {
            switch (ps)
            {
                case PhotoSize.Small:
                    //shrink to a maximum side of 25 pixels
                    {
                        Size tmp = new Size(25, 25);
                        if (_Bitmap != null)
                            tmp = Bitmap.Size;
                        else if (_Icon != null)
                            tmp = _Icon.Size;
                        int max = tmp.Width > tmp.Height ? tmp.Width : tmp.Height;
                        double factor = 25.0 / (double)max;
                        return new Size((int)(tmp.Width * factor), (int)(tmp.Height * factor));
                    }
                case PhotoSize.Medium:
                    //shrink to a maximum side of 64 pixels
                    {
                        Size tmp = new Size(64, 64);
                        if (_Bitmap != null)
                            tmp = Bitmap.Size;
                        else if (_Icon != null)
                            tmp = _Icon.Size;
                        int max = tmp.Width > tmp.Height ? tmp.Width : tmp.Height;
                        double factor = 64.0 / (double)max;
                        return new Size((int)(tmp.Width * factor), (int)(tmp.Height * factor));
                    }
                case PhotoSize.Large:
                    //full size of Bitmap/Icon
                    {
                        if (_Bitmap != null)
                            return _Bitmap.Size;
                        if (_Icon != null)
                            return _Icon.Size;
                        return new Size(128, 128);
                    }
                default:
                    throw new ArgumentException();
            }
        }

        //for placement on map
        private int _XPixels;
        public int XPixels
        {
            get { return _XPixels; }
            set { _XPixels = value; }
        }

        private int _YPixels;
        public int YPixels
        {
            get { return _YPixels; }
            set { _YPixels = value; }
        }

        public bool MatchesMapCoordinates(int x, int y, PhotoSize ps)
        {
            if (!_ShowOnMap)
                return false;

            if (x < _XPixels)
                return false;
            if (y < _YPixels)
                return false;

            Size tmp = this.GetSize(ps);
            if (x > _XPixels + tmp.Width)
                return false;
            if (y > _YPixels + tmp.Height)
                return false;

            return true;
        }

        public override string ToString()
        {
            return _FileName;
        }

        public void Save()
        {
            SaveAs(_FullFileName);
        }

        public void SaveAs(string fileName)
        {
            _ExifFile.Save(fileName, false);
            //reset the time values of the file
            FileInfo fi = new FileInfo(fileName);
            fi.LastWriteTimeUtc = _GPSTime;
            if (fi.CreationTimeUtc != _GPSTime)
                fi.CreationTimeUtc = _GPSTime;
        }
    }
}
